package com.pigdroid.adbsqlite;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Properties;

import com.pigdroid.adbsqlite.manager.ADBSQLiteManager;
import com.pigdroid.adbsqlite.manager.ConsoleLineListener;



public class ADBSQLiteHelper implements ConsoleLineListener {
	
	private ADBSQLiteManager manager;
	private StringBuffer currentResponse = new StringBuffer();
	private StringBuffer currentError = new StringBuffer();
	private HandlerState state;
	private Object lock = new Object();
	
	private static abstract class HandlerState {
		public abstract void processMessage(ADBSQLiteHelper handler, String line);
		public abstract void processErrorMessage(ADBSQLiteHelper handler, String line);
		public abstract void processCommand(ADBSQLiteHelper handler, String cmd);
	}
	
	private static HandlerState stateWaitingUserInput = new HandlerState() {
		
		@Override
		public void processMessage(ADBSQLiteHelper handler, String line) {
			// discard everything until user sends something
		}
		
		@Override
		public void processCommand(ADBSQLiteHelper handler, String cmd) {
			handler.currentResponse.setLength(0);
			handler.currentError.setLength(0);
			handler.state = stateWaitingDBResponse;
			handler.manager.sendCommand(cmd);
		}

		@Override
		public void processErrorMessage(ADBSQLiteHelper handler, String line) {
			handler.currentError.append("\n").append(line);
		}
		
	};

	private static final HandlerState stateWaitingDBResponse = new HandlerState() {
		
		@Override
		public void processMessage(ADBSQLiteHelper handler, String line) {
			// every line counts until we're in statistics
			if (line.startsWith("Memory Used:")) {
				//got there, change status
				handler.state = stateDiscardingStats;
			} else {
				handler.currentResponse.append('\n').append(line);
			}
		}
		
		@Override
		public void processCommand(ADBSQLiteHelper handler, String cmd) {
			// Discard everithing until we got an answer from the server
		}

		@Override
		public void processErrorMessage(ADBSQLiteHelper handler, String line) {
			handler.currentError.append(line);
			handler.state = stateWaitingUserInput;
		}
	};

	private static final HandlerState stateDiscardingStats = new HandlerState() {
		
		@Override
		public void processMessage(ADBSQLiteHelper handler, String line) {
			// every line counts until we're done with the statistics
			if (line.startsWith("Autoindex Inserts:")) {
				//got there, change status
				handler.state = stateWaitingUserInput;
			}
		}
		
		@Override
		public void processCommand(ADBSQLiteHelper handler, String cmd) {
			// Discard everithing until we got an answer from the server
		}

		@Override
		public void processErrorMessage(ADBSQLiteHelper handler, String line) {
			// Discard everithing
			
		}
	};

	public ADBSQLiteHelper(Properties props) throws SQLException, IOException {
		mapProperties(props);
		this.manager = new ADBSQLiteManager(props);
		manager.registerStdoutListener(this);
		manager.registerErrorListener(this);
		this.state = stateWaitingUserInput;
	}
	
	private void mapProperties(Properties ret) throws SQLException {
		Integer port = Integer.valueOf(ret.getProperty("port"));
		String pkg = ret.getProperty("packageName");
		String db = ret.getProperty("dbName");
		String adb = ret.getProperty("adb");
		if (adb == null) {
			throw new SQLException("Use a properties object with complete adb route stored under the key 'adb'.");
		}
		ret.put(ADBSQLiteManager.KEY_DBNAME, db);
		ret.put(ADBSQLiteManager.KEY_PKG, pkg);
		ret.put(ADBSQLiteManager.KEY_COMMAND_PORT, port.toString());
		ret.put(ADBSQLiteManager.KEY_OUTPUT_PORT, Integer.toString(port + 1));
		ret.put(ADBSQLiteManager.KEY_ERROR_PORT, Integer.toString(port + 2));
		ret.put(ADBSQLiteManager.KEY_ADB_ROUTE, adb);
	}

	public void onLineReceived(String portName, String line) {
		synchronized (lock) {
			if ("error".equals(portName)) {
				this.state.processErrorMessage(this, line);
			} else {
				this.state.processMessage(this, line);
			}
		}
	}
	
	public synchronized CSVIterator executeQuery(String sql) throws SQLException {
		this.state.processCommand(this, sql);
		while (this.state != stateWaitingUserInput) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
			}
		}
		if (currentError.length() > 0) {
			throw new SQLException(currentError.toString());
		}
		return new CSVIterator(currentResponse.toString());
	}

	public String getSchema() {
		return manager.getSchema();
	}
	
	public String getErrorMsg() {
		return currentError.toString();
	}
	
	public void close() throws IOException, InterruptedException {
		manager.close();
	}

}
